package gbase

import (
	"bytes"
	"encoding/json"
	"github.com/beego/beego/v2/client/httplib"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"strings"
	"time"
)

type Req struct {
	client  *httplib.BeegoHTTPRequest
	headers Map
	url     string
	method  string

	//1.application/json 2.application/x-www-form-urlencoded 3....
	contentType int8

	//请求超时时间，默认60秒
	timeout int

	//请求参数body 优先级高于params
	body string

	//请求参数params
	params Map
}

func (this *Req) WithHeaders(headers Map) *Req {
	for k, _ := range headers {
		this.headers[k] = headers.GetString(k)
	}
	return this
}

func (this *Req) WithJsonHeader() *Req {
	this.headers["Accept"] = "application/json, text/javascript, */*; q=0.01"
	this.headers["Content-Type"] = "application/json"
	this.contentType = ReqContentTypeJson
	return this
}

func (this *Req) WithXWwwFormUrlencoded() *Req {
	this.headers["Accept"] = "application/json, text/javascript, */*; q=0.01"
	this.headers["Content-Type"] = "application/x-www-form-urlencoded"
	this.contentType = ReqContentTypeXWwwFormUrlencoded
	return this
}

func (this *Req) WithMultiFormData() *Req {
	this.headers["Accept"] = "application/json, text/javascript, */*; q=0.01"
	this.headers["Content-Type"] = "multipart/form-data"
	this.contentType = ReqContentTypeFormData
	return this
}

func (this *Req) WithUrl(url string) *Req {
	this.url = url
	return this
}

func (this *Req) WithMethod(method string) *Req {
	this.method = method
	return this
}

func (this *Req) WithTimeout(timeout int) *Req {
	this.timeout = timeout
	return this
}

func (this *Req) WithParams(params Map) *Req {
	this.params = params
	return this
}

func (this *Req) IsJsonContentType() bool {
	return this.contentType == ReqContentTypeJson
}

func (this *Req) IsXmlContentType() bool {
	return this.contentType == ReqContentTypeXml
}

func (this *Req) IsGet() bool {
	return strings.ToUpper(this.method) == "GET"
}

func (this *Req) IsPost() bool {
	return strings.ToUpper(this.method) == "POST"
}

func (this *Req) GetClient() *httplib.BeegoHTTPRequest {
	return this.client
}

// 依赖this.url,this.method
func (this *Req) Build() *Req {
	this.client = httplib.NewBeegoRequest(this.url, strings.ToUpper(this.method))
	if this.timeout > 0 {
		this.client.SetTimeout(60*time.Second, time.Second*time.Duration(this.timeout))
	}
	return this
}

// 依赖this.Build
func (this *Req) PostFile(filePath string, key ...string) *Req {
	reqKey := "file"
	if len(key) > 0 {
		reqKey = key[0]
	}
	this.client.PostFile(reqKey, filePath)
	return this
}

func (this *Req) handleBody() (err error) {
	if this.body != "" {
		hlog.Debugf("Send POST API url:%s,%s", this.url, this.body)
		this.client.Body([]byte(this.body))
	} else if this.params != nil {
		switch {
		case this.IsJsonContentType():
			bf := bytes.NewBuffer([]byte{})
			jsonEncoder := json.NewEncoder(bf)
			jsonEncoder.SetEscapeHTML(false)
			if err = jsonEncoder.Encode(this.params); err != nil {
				return err
			}
			this.client.Body(bf.Bytes())
			hlog.Debugf("Send POST API url:%s,%s", this.url, bf.String())
		case this.IsXmlContentType():
			xmlStr := this.params.ToXmlStr()
			this.client.Body(xmlStr)
			hlog.Debugf("Send POST API url:%s,%s", this.url, xmlStr)
		default:
			for k, _ := range this.params {
				this.client.Param(k, this.params.GetString(k))
			}
			hlog.Debugf("Send POST API url:%s,%s", this.url, this.params.ToString())
		}
	}
	return nil
}

// 这里的params优先级高于this.params
func (this *Req) Do(params ...Map) (resp *Resp) {
	var (
		err     error
		respStr string
	)
	resp = &Resp{}
	if len(params) > 0 {
		this.params = params[0]
	}

	switch {
	case this.IsGet():
		if this.client == nil {
			this.client = httplib.Get(this.url)
		}
		if this.params != nil {
			for k, _ := range this.params {
				this.client.Param(k, this.params.GetString(k))
			}
		}
		hlog.Debugf("Send GET API url:%s", this.client.GetRequest().URL)
	case this.IsPost():
		if this.client == nil {
			this.client = httplib.Post(this.url)
		}

		if err = this.handleBody(); err != nil {
			return NewFailResp(100, err.Error())
		}

		//if this.params != nil {
		//
		//}
	default:
		hlog.Errorf("不支持的请求类型:%s", this.method)
		return NewFailResp(100, "不支持的请求类型")
	}

	for k, _ := range this.headers {
		this.client.Header(k, this.headers.GetString(k))
	}

	respStr, err = this.client.String()
	hlog.Debugf("%s 响应:%s", this.url, respStr)
	if err != nil {
		return NewFailResp(100, "请求失败")
	}
	return NewSuccessResp(200, respStr, "ok")
}

// 简化版：NewReq().WithJsonHeader().Send(method,url,params)
func (this *Req) Send(method string, apiUrl string, params Map) (resp *Resp) {
	this.method = method
	this.url = apiUrl
	this.Build()
	return this.Do(params)
}

func NewReq(opts ...Option[*Req]) *Req {
	req := &Req{}
	req.headers = Map{}

	if len(opts) == 0 {
		req.WithJsonHeader()
	}
	for _, opt := range opts {
		opt(req)
	}

	if req.url != "" && req.method != "" {
		req.client = httplib.NewBeegoRequest(req.url, req.method)
	}
	return req
}

/*
usage:
resp := gbase.CallApi(
	WithAdminId(1123),
	WithApi("/admin/admin/list", true),
	gbase.WithParams(&usermodel.AdminList{
		PageReq: gbase.PageReq{
			Page:  1,
			Limit: 10,
		},
		AdminRoleId: 5,
	}),
)

define in project:
func WithAdminId(uid int64) gbase.Option[*gbase.Req] {
	return func(req *gbase.Req) {
		token, _ := wsservice.AdminTokenGet(&models.Admin{Id: uid})
		req.WithHeaders(gbase.Map{
			"Authorization": fmt.Sprintf("Bearer %s", token),
		})
	}
}

func WithApi(api string, remote ...bool) gbase.Option[*gbase.Req] {
	return func(req *gbase.Req) {
		callApi := fmt.Sprintf("http://127.0.0.1:%s%s", global.AppConf().Ports.Http.GxUser,api)
		if len(remote) > 0 && remote[0] {
			callApi = fmt.Sprintf("https://svr.machine.inlemer.cn/gxuser%s", api)
		}
		req.WithUrl(callApi)
	}
}

*/

func defaultOpts() (opts []Option[*Req]) {
	opts = make([]Option[*Req], 0)
	opts = append(opts,
		WithReqTimeout(60),
		WithReqContentTypeJson(),
		WithReqMethod("POST"),
	)
	return
}

// 通用调用接口，简化初始货过程
func CallApi(opts ...Option[*Req]) (resp *Resp) {
	req := NewReq() //.WithTimeout(3600).WithJsonHeader().WithMethod("post")
	if len(opts) == 0 {
		opts = make([]Option[*Req], 0)
	}
	opts = append(defaultOpts(), opts...)
	for _, opt := range opts {
		opt(req)
	}
	return req.Do()
}

func WithReqHeaders(headers Map) Option[*Req] {
	return func(req *Req) {
		for k, _ := range headers {
			req.headers[k] = headers.GetString(k)
		}
	}
}

func WithReqUrl(url string) Option[*Req] {
	return func(req *Req) {
		req.url = url
	}
}

func WithReqMethod(method string) Option[*Req] {
	return func(req *Req) {
		req.method = strings.ToUpper(method)
	}
}

func WithReqTimeout(timeout int) Option[*Req] {
	return func(req *Req) {
		req.timeout = timeout
	}
}

func WithReqParams(params any) Option[*Req] {
	return func(req *Req) {
		cm := Map{}
		_ = cm.FromX(params)
		req.WithParams(cm)
	}
}

func WithReqBody(body string) Option[*Req] {
	return func(req *Req) {
		req.body = body
	}
}

//func WithReqFile(filepath string) Option[*Req] {
//	return func(req *Req) {
//		req.PostFile()
//		req.body = body
//	}
//}

func WithReqContentTypeJson() Option[*Req] {
	return func(req *Req) {
		req.headers["Accept"] = "application/json, text/javascript, */*; q=0.01"
		req.headers["Content-Type"] = "application/json"
		req.contentType = ReqContentTypeJson
	}
}

func WithReqContentTypeXWwwFormUrlencoded() Option[*Req] {
	return func(req *Req) {
		req.headers["Accept"] = "application/json, text/javascript, */*; q=0.01"
		req.headers["Content-Type"] = "application/x-www-form-urlencoded"
		req.contentType = ReqContentTypeXWwwFormUrlencoded
	}
}

func WithReqContentTypeMultiFormData() Option[*Req] {
	return func(req *Req) {
		req.headers["Accept"] = "application/json, text/javascript, */*; q=0.01"
		req.headers["Content-Type"] = "multipart/form-data"
		req.contentType = ReqContentTypeFormData
	}
}
func WithReqContentTypeXml() Option[*Req] {
	return func(req *Req) {
		req.headers["Accept"] = "application/json, text/javascript, */*; q=0.01"
		req.headers["Content-Type"] = "application/xml"
		req.contentType = ReqContentTypeXml
	}
}
